﻿using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using StackExchange.Redis.Maintenance;
using StackExchange.Redis.Profiling;
using static StackExchange.Redis.ConnectionMultiplexer;

namespace StackExchange.Redis;

internal interface IInternalConnectionMultiplexer : IConnectionMultiplexer
{
    bool AllowConnect { get; set; }

    bool IgnoreConnect { get; set; }

    ReadOnlySpan<ServerEndPoint> GetServerSnapshot();
    ServerEndPoint GetServerEndPoint(EndPoint endpoint);

    ConfigurationOptions RawConfig { get; }

    long? GetConnectionId(EndPoint endPoint, ConnectionType type);

    ServerSelectionStrategy ServerSelectionStrategy { get; }

    int GetSubscriptionsCount();
    ConcurrentDictionary<RedisChannel, Subscription> GetSubscriptions();

    ConnectionMultiplexer UnderlyingMultiplexer { get; }
}

/// <summary>
/// Represents the abstract multiplexer API.
/// </summary>
public interface IConnectionMultiplexer : IDisposable, IAsyncDisposable
{
    /// <summary>
    /// Gets the client-name that will be used on all new connections.
    /// </summary>
    string ClientName { get; }

    /// <summary>
    /// Gets the configuration of the connection.
    /// </summary>
    string Configuration { get; }

    /// <summary>
    /// Gets the timeout associated with the connections.
    /// </summary>
    int TimeoutMilliseconds { get; }

    /// <summary>
    /// The number of operations that have been performed on all connections.
    /// </summary>
    long OperationCount { get; }

    /// <summary>
    /// Gets or sets whether asynchronous operations should be invoked in a way that guarantees their original delivery order.
    /// </summary>
    [Obsolete("Not supported; if you require ordered pub/sub, please see " + nameof(ChannelMessageQueue), false)]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    bool PreserveAsyncOrder { get; set; }

    /// <summary>
    /// Indicates whether any servers are connected.
    /// </summary>
    bool IsConnected { get; }

    /// <summary>
    /// Indicates whether any servers are connecting.
    /// </summary>
    bool IsConnecting { get; }

    /// <summary>
    /// Should exceptions include identifiable details? (key names, additional <see cref="Exception.Data"/> annotations).
    /// </summary>
    [Obsolete($"Please use {nameof(ConfigurationOptions)}.{nameof(ConfigurationOptions.IncludeDetailInExceptions)} instead - this will be removed in 3.0.")]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    bool IncludeDetailInExceptions { get; set; }

    /// <summary>
    /// Limit at which to start recording unusual busy patterns (only one log will be retained at a time.
    /// Set to a negative value to disable this feature).
    /// </summary>
    int StormLogThreshold { get; set; }

    /// <summary>
    /// Register a callback to provide an on-demand ambient session provider based on the calling context.
    /// The implementing code is responsible for reliably resolving the same provider
    /// based on ambient context, or returning null to not profile.
    /// </summary>
    /// <param name="profilingSessionProvider">The profiling session provider.</param>
    void RegisterProfiler(Func<ProfilingSession?> profilingSessionProvider);

    /// <summary>
    /// Get summary statistics associates with this server.
    /// </summary>
    ServerCounters GetCounters();

    /// <summary>
    /// A server replied with an error message.
    /// </summary>
    event EventHandler<RedisErrorEventArgs> ErrorMessage;

    /// <summary>
    /// Raised whenever a physical connection fails.
    /// </summary>
    event EventHandler<ConnectionFailedEventArgs> ConnectionFailed;

    /// <summary>
    /// Raised whenever an internal error occurs (this is primarily for debugging).
    /// </summary>
    event EventHandler<InternalErrorEventArgs> InternalError;

    /// <summary>
    /// Raised whenever a physical connection is established.
    /// </summary>
    event EventHandler<ConnectionFailedEventArgs> ConnectionRestored;

    /// <summary>
    /// Raised when configuration changes are detected.
    /// </summary>
    event EventHandler<EndPointEventArgs> ConfigurationChanged;

    /// <summary>
    /// Raised when nodes are explicitly requested to reconfigure via broadcast.
    /// This usually means primary/replica changes.
    /// </summary>
    event EventHandler<EndPointEventArgs> ConfigurationChangedBroadcast;

    /// <summary>
    /// Raised when server indicates a maintenance event is going to happen.
    /// </summary>
    event EventHandler<ServerMaintenanceEvent> ServerMaintenanceEvent;

    /// <summary>
    /// Gets all endpoints defined on the multiplexer.
    /// </summary>
    /// <param name="configuredOnly">Whether to return only the explicitly configured endpoints.</param>
    EndPoint[] GetEndPoints(bool configuredOnly = false);

    /// <summary>
    /// Wait for a given asynchronous operation to complete (or timeout).
    /// </summary>
    /// <param name="task">The task to wait on.</param>
    void Wait(Task task);

    /// <summary>
    /// Wait for a given asynchronous operation to complete (or timeout).
    /// </summary>
    /// <typeparam name="T">The type in <paramref name="task"/>.</typeparam>
    /// <param name="task">The task to wait on.</param>
    T Wait<T>(Task<T> task);

    /// <summary>
    /// Wait for the given asynchronous operations to complete (or timeout).
    /// </summary>
    /// <param name="tasks">The tasks to wait on.</param>
    void WaitAll(params Task[] tasks);

    /// <summary>
    /// Raised when a hash-slot has been relocated.
    /// </summary>
    event EventHandler<HashSlotMovedEventArgs> HashSlotMoved;

    /// <summary>
    /// Compute the hash-slot of a specified key.
    /// </summary>
    /// <param name="key">The key to get a slot ID for.</param>
    int HashSlot(RedisKey key);

    /// <summary>
    /// Obtain a pub/sub subscriber connection to the specified server.
    /// </summary>
    /// <param name="asyncState">The async state to pass to the created <see cref="ISubscriber"/>.</param>
    ISubscriber GetSubscriber(object? asyncState = null);

    /// <summary>
    /// Obtain an interactive connection to a database inside redis.
    /// </summary>
    /// <param name="db">The database ID to get.</param>
    /// <param name="asyncState">The async state to pass to the created <see cref="IDatabase"/>.</param>
    IDatabase GetDatabase(int db = -1, object? asyncState = null);

    /// <summary>
    /// Obtain a configuration API for an individual server.
    /// </summary>
    /// <param name="host">The host to get a server for.</param>
    /// <param name="port">The specific port for <paramref name="host"/> to get a server for.</param>
    /// <param name="asyncState">The async state to pass to the created <see cref="IServer"/>.</param>
    IServer GetServer(string host, int port, object? asyncState = null);

    /// <summary>
    /// Obtain a configuration API for an individual server.
    /// </summary>
    /// <param name="hostAndPort">The "host:port" string to get a server for.</param>
    /// <param name="asyncState">The async state to pass to the created <see cref="IServer"/>.</param>
    IServer GetServer(string hostAndPort, object? asyncState = null);

    /// <summary>
    /// Obtain a configuration API for an individual server.
    /// </summary>
    /// <param name="host">The host to get a server for.</param>
    /// <param name="port">The specific port for <paramref name="host"/> to get a server for.</param>
    IServer GetServer(IPAddress host, int port);

    /// <summary>
    /// Obtain a configuration API for an individual server.
    /// </summary>
    /// <param name="endpoint">The endpoint to get a server for.</param>
    /// <param name="asyncState">The async state to pass to the created <see cref="IServer"/>.</param>
    IServer GetServer(EndPoint endpoint, object? asyncState = null);

    /// <summary>
    /// Gets a server that would be used for a given key and flags.
    /// </summary>
    /// <param name="key">The endpoint to get a server for. In a non-cluster environment, this parameter is ignored. A <see langword="default"/> key may be specified
    /// on cluster, which will return a connection to an arbitrary server matching the specified flags.</param>
    /// <param name="asyncState">The async state to pass to the created <see cref="IServer"/>.</param>
    /// <param name="flags">The command flags to use.</param>
    /// <remarks>This method is particularly useful when communicating with a cluster environment, to obtain a connection to the server that owns the specified key
    /// and ad-hoc commands with unusual routing requirements. Note that <see cref="GetDatabase"/> provides a connection that automatically routes commands by
    /// looking for <see cref="RedisKey"/> parameters, so this method is only necessary when used with commands that do not take a <see cref="RedisKey"/> parameter,
    /// but require consistent routing using key-like semantics.</remarks>
    IServer GetServer(RedisKey key, object? asyncState = null, CommandFlags flags = CommandFlags.None);

    /// <summary>
    /// Obtain configuration APIs for all servers in this multiplexer.
    /// </summary>
    IServer[] GetServers();

    /// <summary>
    /// Reconfigure the current connections based on the existing configuration.
    /// </summary>
    /// <param name="log">The log to write output to.</param>
    Task<bool> ConfigureAsync(TextWriter? log = null);

    /// <summary>
    /// Reconfigure the current connections based on the existing configuration.
    /// </summary>
    /// <param name="log">The log to write output to.</param>
    bool Configure(TextWriter? log = null);

    /// <summary>
    /// Provides a text overview of the status of all connections.
    /// </summary>
    string GetStatus();

    /// <summary>
    /// Provides a text overview of the status of all connections.
    /// </summary>
    /// <param name="log">The log to write output to.</param>
    void GetStatus(TextWriter log);

    /// <summary>
    /// See <see cref="object.ToString"/>.
    /// </summary>
    string ToString();

    /// <summary>
    /// Close all connections and release all resources associated with this object.
    /// </summary>
    /// <param name="allowCommandsToComplete">Whether to allow in-queue commands to complete first.</param>
    void Close(bool allowCommandsToComplete = true);

    /// <summary>
    /// Close all connections and release all resources associated with this object.
    /// </summary>
    /// <param name="allowCommandsToComplete">Whether to allow in-queue commands to complete first.</param>
    Task CloseAsync(bool allowCommandsToComplete = true);

    /// <summary>
    /// Obtains the log of unusual busy patterns.
    /// </summary>
    string? GetStormLog();

    /// <summary>
    /// Resets the log of unusual busy patterns.
    /// </summary>
    void ResetStormLog();

    /// <summary>
    /// Request all compatible clients to reconfigure or reconnect.
    /// </summary>
    /// <param name="flags">The command flags to use.</param>
    /// <returns>The number of instances known to have received the message (however, the actual number can be higher; returns -1 if the operation is pending).</returns>
    long PublishReconfigure(CommandFlags flags = CommandFlags.None);

    /// <summary>
    /// Request all compatible clients to reconfigure or reconnect.
    /// </summary>
    /// <param name="flags">The command flags to use.</param>
    /// <returns>The number of instances known to have received the message (however, the actual number can be higher).</returns>
    Task<long> PublishReconfigureAsync(CommandFlags flags = CommandFlags.None);

    /// <summary>
    /// Get the hash-slot associated with a given key, if applicable; this can be useful for grouping operations.
    /// </summary>
    /// <param name="key">The key to get a the slot for.</param>
    int GetHashSlot(RedisKey key);

    /// <summary>
    /// Write the configuration of all servers to an output stream.
    /// </summary>
    /// <param name="destination">The destination stream to write the export to.</param>
    /// <param name="options">The options to use for this export.</param>
    void ExportConfiguration(Stream destination, ExportOptions options = ExportOptions.All);

    /// <summary>
    /// Append a usage-specific modifier to the advertised library name; suffixes are de-duplicated
    /// and sorted alphabetically (so adding 'a', 'b' and 'a' will result in suffix '-a-b').
    /// Connections will be updated as necessary (RESP2 subscription
    /// connections will not show updates until those connections next connect).
    /// </summary>
    void AddLibraryNameSuffix(string suffix);
}
